/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Common Public License v0.5 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v05.html
 *
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core;

import com.google.common.collect.Lists;

import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.internal.AdditionMarkerManager;
import net.sourceforge.perforce.core.internal.BackgroundUpdateRunnable;
import net.sourceforge.perforce.core.internal.ConnectionMonitorRunnable;
import net.sourceforge.perforce.core.internal.Policy;
import net.sourceforge.perforce.core.internal.api.PerforceServerManager;
import net.sourceforge.perforce.core.internal.events.AddDeleteMoveListener;
import net.sourceforge.perforce.core.internal.events.MetaFileChangeListener;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.syncinfo.IResourceStateDelta;

import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.BundleContext;

import java.util.List;

/**
 * @version $Revision: 494 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net>Boris
 *         Pruessmann</a>
 */
public class PerforceCorePlugin extends Plugin {
  //----------------------------------------------------------------------------
  // Public Constants

  /** The identifier of the plugin */
  public static final String ID = "net.sourceforge.perforce.core"; //$NON-NLS-1$

  // ---------------------------------------------------------------------------
  // Private Constants

  /** Identifier for the plugin's nature. */
  private static final String NATURE_ID = ID + ".perforcenature"; //$NON-NLS-1$

  // --------------------------------------------------------------------
  // Private Static Variables

  /** Stores the plugin's singleton instance. */
  private static PerforceCorePlugin INSTANCE;

  // ---------------------------------------------------------------------------
  // Private Variables

  /** Stores the PerforceServerManager. */
  private PerforceServerManager serverManager;

  /** Contains a list of IResourceStateChangeListeners. */
  private List<IResourceStateChangeListener> resourceStateChangeListenerList =
      Lists.newArrayList();

  /** Stores a list of IChangelistListeners. */
  private List<IChangelistListener> changelistListenerList = Lists.newArrayList();

  /** Stores a list of IClientUserListeners. */
  private List<IClientUserListener> clientUserListenerList = Lists.newArrayList();

  /** Stores the connection monitor runnable. */
  private ConnectionMonitorRunnable connectionMonitor;

  /** Stores the background update runnable. */
  private BackgroundUpdateRunnable backgroundUpdateRunnable;

  /** Stores the background update runnable. */
  private AdditionMarkerManager additionMarkerManager;
  /** Stores the AddDeleteMoveListener. */
  private AddDeleteMoveListener addDeleteMoveListener = new AddDeleteMoveListener();

  /** Stores the MetaFileChangeListener. */
  private MetaFileChangeListener metaFileChangeListener = new MetaFileChangeListener();

  /** true if tasks should be generated, false otherwise. */
  private boolean showTasksOnAdd = false;

  /**
   * Indicates whether delete operations should be performed the 'perforce way'.
   */
  private boolean deleteAffectsChangelist = true;

  /** Stores the interval for background cache updates. */
  private int backgroundUpdateInterval;

  /** Indicates whether the Background Update Thread should monitor changelists. */
  private boolean shouldMonitorChangelists;

  /**
   * Indicates whether the Background Update Thread should monitor opened files.
   */
  private boolean shouldMonitorOpenedFiles;

  /** Submit validators */
  private ISubmitValidator[] submitValidators;

  /** Open validators */
  private IOpenValidator[] openValidators;

  // -----------------------------------------------------------------------
  // Public Static Methods

  /**
   * Returns the plugin's singleton instance.
   *
   * @return the plugin singleton.
   */
  public static PerforceCorePlugin getPlugin() {
    return INSTANCE;
  }

  /**
   * Returns the plugin's nature id.
   *
   * @return the plugin's nature id.
   */
  public static String getTypeId() {
    return NATURE_ID;
  }

  /**
   * Returns the workspace instance.
   *
   * @return the workspace instance.
   */
  public static IWorkspace getWorkspace() {
    return ResourcesPlugin.getWorkspace();
  }

  /**
   * Returns the plugin's synchronizer singleton instance.
   *
   * @return the synchronizer singleton.
   */
  public static PerforceSynchronizer getSynchronizer() {
    return PerforceSynchronizer.getInstance();
  }

  /**
   * Call this method to log the specified status object to the eclipse log.
   *
   * @param status IStatus to be logged.
   */
  public static void log(IStatus status) {
    getPlugin().getLog().log(status);
  }

  /**
   * Call this method to log the specified error message with the associated
   * exception.
   *
   * @param message detailed error message.
   * @param throwable exception that occurred.
   */
  public static void logError(String message, Throwable throwable) {
    getPlugin().getLog().log(
        new Status(IStatus.ERROR, PerforceCorePlugin.ID, IStatus.ERROR, message, throwable));
  }

  /**
   * Returns the the ServerManager singleton to the user.
   *
   * @return the ServerManager singleton.
   */
  public static IPerforceServerManager getServerManager() {
    return getPlugin().serverManager;
  }

  // -------------------------------------------------------------------------
  // Public Constructors

  /**
   * Constructs a new instance.
   */
  public PerforceCorePlugin() {
  }

  //----------------------------------------------------------------------------
  // -- Public Methods

  /**
   * Returns whether deletes should be performed the perforce way.
   *
   * @return true or false.
   */
  public boolean getDeleteAffectsChangelist() {
    return deleteAffectsChangelist;
  }

  /**
   * Changes the way delete operations are performed.
   *
   * @param newDeleteAffectsChangelist true means that we do deletes the
   *        perforce way.
   */
  public void setDeleteAffectsChangelist(boolean newDeleteAffectsChangelist) {
    deleteAffectsChangelist = newDeleteAffectsChangelist;
  }

  /**
   * Returns whether tasks should be generated or not.
   *
   * @return true if tasks should be generated.
   */
  public boolean getShowTasksOnAdd() {
    return showTasksOnAdd;
  }

  /**
   * Sets whether tasks should be generated or not.
   *
   * @param newShowTasksOnAdd true or false.
   */
  public void setShowTasksOnAdd(boolean newShowTasksOnAdd) {
    showTasksOnAdd = newShowTasksOnAdd;
  }

  /**
   * Gets the interval for background updates.
   *
   * @return background update interval.
   */
  public int getBackgroundUpdateInterval() {
    return backgroundUpdateInterval;
  }

  /**
   * Sets the background update interval.
   *
   * @param interval the new background update interval.
   */
  public void setBackgroundUpdateInterval(int interval) {
    backgroundUpdateInterval = interval;
  }

  /**
   * Checks whether the Background Update Thread should watch changelists.
   *
   * @return true or false.
   */
  public boolean shouldMonitorChangelists() {
    return shouldMonitorChangelists;
  }

  /**
   * Changes the Background Update Monitors behavior concerning the monitoring
   * of changelists.
   *
   * @param newValue the new value.
   */
  public void setShouldMonitorChangelists(boolean newValue) {
    shouldMonitorChangelists = newValue;
  }

  /**
   * Checks whether the Background Update Thread should watch opened files.
   *
   * @return true or false.
   */
  public boolean shouldMonitorOpenedFiles() {
    return shouldMonitorOpenedFiles;
  }

  /**
   * Changes the Background Update Monitors behavior concerning the monitoring
   * of opened files.
   *
   * @param newValue the new value.
   */
  public void setShouldMonitorOpenedFiles(boolean newValue) {
    shouldMonitorOpenedFiles = newValue;
  }

  /**
   * If this returns true, the system will try to refresh sync info from the
   * server when the cache does not contain anything.
   *
   * @return true or false.
   *
   *         TODO: Preferences for this feature
   */
  public boolean getShouldRefreshWhenNotFound() {
    return false;
  }

  @Override
  public void start(BundleContext context) throws Exception {
    super.start(context);
    INSTANCE = this;
    Policy.localize("net.sourceforge.perforce.core.messages"); //$NON-NLS-1$

    try {
      serverManager = new PerforceServerManager();
      serverManager.startup();

      connectionMonitor = new ConnectionMonitorRunnable();
      connectionMonitor.startup();

      backgroundUpdateRunnable = new BackgroundUpdateRunnable();
      backgroundUpdateRunnable.startup();

      additionMarkerManager = new AdditionMarkerManager();
      additionMarkerManager.startup();

      PerforceSynchronizer.getInstance().startup();
    } catch (PerforceException e) {
      throw new CoreException(e.getStatus());
    }

    PerforceCorePlugin.getWorkspace().addResourceChangeListener(addDeleteMoveListener,
        IResourceChangeEvent.POST_BUILD | IResourceChangeEvent.PRE_DELETE);
    addResourceStateChangeListener(addDeleteMoveListener);

    PerforceCorePlugin.getWorkspace().addResourceChangeListener(metaFileChangeListener,
        IResourceChangeEvent.PRE_BUILD);
  }

  @Override
  public void stop(BundleContext context) throws Exception {
    // Profiler.dump();
    PerforceCorePlugin.getWorkspace().removeResourceChangeListener(metaFileChangeListener);
    PerforceCorePlugin.getWorkspace().removeResourceChangeListener(addDeleteMoveListener);

    try {
      backgroundUpdateRunnable.shutdown();
      connectionMonitor.shutdown();
      additionMarkerManager.shutdown();

      PerforceSynchronizer.getInstance().shutdown();

      serverManager.shutdown();
    } catch (PerforceException e) {
      throw new CoreException(e.getStatus());
    }

    super.stop(context);
    INSTANCE = null;
  }


  /**
   * Adds the given IResourceStateChangeListener to the notification list.
   *
   * @param listener the IResourceStateChangeListener to be added.
   */
  public void addResourceStateChangeListener(IResourceStateChangeListener listener) {
    resourceStateChangeListenerList.add(listener);
  }

  /**
   * Removes the specified IResourceStateChangeListener from the notification
   * list.
   *
   * @param listener the listener that should be removed.
   */
  public void removeResourceStateChangeListener(IResourceStateChangeListener listener) {
    resourceStateChangeListenerList.remove(listener);
  }

  /**
   * Call this method to notify registers IResourceStateChangeListeners that the
   * given resources have changed their state.
   *
   * @param deltas the ResourceStateDeltas.
   *
   * @return the status of the operation.
   */
  public IStatus broadcastResourceStateChanges(final IResourceStateDelta[] deltas) {
    final MultiStatus result = new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null); //$NON-NLS-1$
    for (int i = 0, length = resourceStateChangeListenerList.size(); i < length; i++) {
      final IResourceStateChangeListener listener = resourceStateChangeListenerList.get(i);

      ISafeRunnable runnable = new ISafeRunnable() {
        public void handleException(Throwable exception) {
          logError("Error in broadcastResourceStateChanges()", exception);
        }

        public void run() {
          try {
            listener.resourceStateChanged(deltas);
          } catch (Throwable e) {
            handleException(e);
          }
        }
      };

      Platform.run(runnable);
    }

    return result;
  }

  /**
   * Call this method to notify registered IResourceStateChangeListeners about
   * changes to the given changelists.
   *
   * @param changelists the changelists.
   */
  public void broadcastChangelistStateChanges(final IChangelist[] changelists) {
    for (int i = 0, length = resourceStateChangeListenerList.size(); i < length; i++) {
      final IResourceStateChangeListener listener = resourceStateChangeListenerList.get(i);

      ISafeRunnable runnable = new ISafeRunnable() {
        public void handleException(Throwable exception) {
          logError("Error in broadcastChangelistStateChanges()", exception);
        }

        public void run() throws Exception {
          listener.changelistStateChanged(changelists);
        }
      };

      SafeRunner.run(runnable);
    }
  }

  /**
   * Call this method to notify registered listeners about changes to the
   * changelists of the given servers.
   *
   * @param servers the servers.
   */
  public void broadcastChangelistStateChanges(final IPerforceServer[] servers) {
    for (int i = 0, length = resourceStateChangeListenerList.size(); i < length; i++) {
      final IResourceStateChangeListener listener = resourceStateChangeListenerList.get(i);

      ISafeRunnable runnable = new ISafeRunnable() {
        /** @see ISafeRunnable#handleException(Throwable) */
        public void handleException(Throwable exception) {
          logError("Error in broadcastChangelistStateChanges()", exception);
        }

        public void run() throws Exception {
          listener.changelistStateChanged(servers);
        }
      };

      SafeRunner.run(runnable);
    }
  }

  /**
   * Adds the given listener to the changelist notification list.
   *
   * @param listener that should be added.
   */
  public void addChangelistListener(IChangelistListener listener) {
    changelistListenerList.add(listener);
  }

  /**
   * Removes the specified listener from the changelist notification list.
   *
   * @param listener to remove.
   */
  public void removeChangelistListener(IChangelistListener listener) {
    changelistListenerList.remove(listener);
  }

  /**
   * Adds the given IClientUserListener to the listener list.
   *
   * @param listener the listener to add.
   */
  public void addClientUserListener(IClientUserListener listener) {
    clientUserListenerList.add(listener);
  }

  /**
   * Removes the given IClientUserListener from the listener list.
   *
   * @param listener the listener to add.
   */
  public void removeClientUserListener(IClientUserListener listener) {
    clientUserListenerList.remove(listener);
  }

  /**
   * Returns an array of client user listeners.
   *
   * @return array of listeners.
   */
  public IClientUserListener[] getClientUserListeners() {
    return clientUserListenerList.toArray(new IClientUserListener[clientUserListenerList.size()]);
  }

  /**
   * Notifies that registered listeners that one or more changelist have been
   * changed.
   */
  public void broadcastChangelistChanges() {
    for (int i = 0, length = changelistListenerList.size(); i < length; i++) {
      final IChangelistListener listener = changelistListenerList.get(i);

      ISafeRunnable runnable = new ISafeRunnable() {
        public void handleException(Throwable exception) {
          logError("Error in broadcastChangelistChanges()", exception);
        }

        public void run() throws Exception {
          listener.notifyChange();
        }
      };
      SafeRunner.run(runnable);
    }
  }

  /**
   * Called to retrieve an array of submit validators.
   *
   * @return array of submit validators.
   */
  public synchronized ISubmitValidator[] getSubmitValidators() {
    if (submitValidators == null) {
      submitValidators = computeSubmitValidators();
    }

    return submitValidators;
  }

  /**
   * Called to retrieve an array of open validators.
   *
   * @return array of open validators.
   */
  public synchronized IOpenValidator[] getOpenValidators() {
    if (openValidators == null) {
      openValidators = computeOpenValidators();
    }

    return openValidators;
  }

  //----------------------------------------------------------------------------
  // - Private Methods

  /**
   * Called to compute the submit validators.
   *
   * @return array of submit validators.
   */
  private ISubmitValidator[] computeSubmitValidators() {
    IExtensionRegistry registry = Platform.getExtensionRegistry();
    IExtensionPoint extensionPoint =
        registry.getExtensionPoint(PerforceCorePlugin.ID, "submitValidators"); //$NON-NLS-1$
    IConfigurationElement[] configurationElements = extensionPoint.getConfigurationElements();

    List<ISubmitValidator> result = Lists.newArrayListWithExpectedSize(configurationElements.length);
    for (int i = 0; i < configurationElements.length; i++) {
      IConfigurationElement element = configurationElements[i];
      try {
        ISubmitValidator validator = (ISubmitValidator) element.createExecutableExtension("class"); //$NON-NLS-1$
        result.add(validator);
      } catch (CoreException e) {
        PerforceCorePlugin.log(e.getStatus());
      }
    }

    return result.toArray(new ISubmitValidator[result.size()]);
  }

  /**
   * Called to compute the open validators.
   *
   * @return array of open validators.
   */
  private IOpenValidator[] computeOpenValidators() {
    IExtensionRegistry registry = Platform.getExtensionRegistry();
    IExtensionPoint extensionPoint =
        registry.getExtensionPoint(PerforceCorePlugin.ID, "openValidators"); //$NON-NLS-1$
    IConfigurationElement[] configurationElements = extensionPoint.getConfigurationElements();

    List<IOpenValidator> result = Lists.newArrayListWithExpectedSize(configurationElements.length);
    for (int i = 0; i < configurationElements.length; i++) {
      IConfigurationElement element = configurationElements[i];
      try {
        IOpenValidator validator = (IOpenValidator) element.createExecutableExtension("class"); //$NON-NLS-1$
        result.add(validator);
      } catch (CoreException e) {
        PerforceCorePlugin.log(e.getStatus());
      }
    }

    return result.toArray(new IOpenValidator[result.size()]);
  }

  public void info(String msg) {
    log(new MultiStatus(ID, 0, msg, null));
  }
}
